import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as fs from 'fs/promises';
import * as crypto from 'crypto';
import * as path from 'path';
import * as sharp from 'sharp';

// 异常
import {
  FileValidationException,
  ExceptionUtils,
  ErrorCode,
} from './exceptions/scoring.exceptions';

// 类型定义
interface ValidationOptions {
  user_id: number;
  module: string;
  correlation_id?: string;
}

interface ValidationResult {
  filePath: string;
  relativePath: string;
  fileHash: string;
  metadata: {
    size: number;
    mimeType: string;
    dimensions: {
      width: number;
      height: number;
    };
    format: string;
    hasAlpha: boolean;
    channels: number;
  };
}

/**
 * 文件验证服务 - 零隐患MFIS规范实现
 * 解决隐患：#13 同步I/O阻塞，#6 内存泄漏，#14 日志信息泄露
 */
@Injectable()
export class FileValidationService {
  private readonly logger = new Logger(FileValidationService.name);
  private readonly uploadPath: string;
  private readonly maxFileSize: number;
  private readonly allowedMimeTypes: string[];
  private readonly minDimensions: { width: number; height: number };
  private readonly maxDimensions: { width: number; height: number };

  constructor(private readonly configService: ConfigService) {
    this.uploadPath = this.configService.get<string>('UPLOAD_PATH', './uploads/medical_images');
    this.maxFileSize = this.configService.get<number>('MAX_FILE_SIZE', 10 * 1024 * 1024);
    this.allowedMimeTypes = [
      'image/jpeg',
      'image/jpg',
      'image/png',
      'image/tiff',
      'image/bmp',
    ];
    this.minDimensions = { width: 100, height: 100 };
    this.maxDimensions = { width: 4096, height: 4096 };

    this.initializeUploadDirectory();
  }

  /**
   * 初始化上传目录
   * 解决隐患：#13 异步I/O操作
   */
  private async initializeUploadDirectory(): Promise<void> {
    try {
      await fs.access(this.uploadPath);
    } catch {
      await fs.mkdir(this.uploadPath, { recursive: true });
      this.logger.log(`创建上传目录: ${this.uploadPath}`);
    }
  }

  /**
   * 验证并保存文件
   * 解决隐患：#13 全面使用异步I/O，#14 日志脱敏
   */
  async validateAndSaveFile(
    file: Express.Multer.File,
    options: ValidationOptions,
  ): Promise<ValidationResult> {
    const startTime = Date.now();

    this.logger.log('开始文件验证', {
      filename: file.originalname,
      size: file.size,
      mimetype: file.mimetype,
      user_id: options.user_id,
      module: options.module,
      correlation_id: options.correlation_id,
    });

    try {
      // 1. 基础属性验证
      await this.validateBasicProperties(file);

      // 2. 异步文件哈希计算
      const fileHash = await this.calculateFileHash(file.path);

      // 3. 异步图像元数据验证
      const imageMetadata = await this.validateImageProperties(file.path);

      // 4. 安全文件保存
      const savedPath = await this.saveFileSecurely(file, fileHash, options);

      // 5. 生成相对路径
      const relativePath = path.relative(process.cwd(), savedPath);

      const validationResult: ValidationResult = {
        filePath: savedPath,
        relativePath,
        fileHash,
        metadata: {
          size: file.size,
          mimeType: file.mimetype,
          dimensions: {
            width: imageMetadata.width,
            height: imageMetadata.height,
          },
          format: imageMetadata.format,
          hasAlpha: imageMetadata.hasAlpha,
          channels: imageMetadata.channels,
        },
      };

      const processingTime = Date.now() - startTime;

      this.logger.log('文件验证完成', {
        filename: file.originalname,
        file_hash: fileHash.substring(0, 16) + '...',
        saved_path: this.sanitizePath(savedPath),
        processing_time_ms: processingTime,
        correlation_id: options.correlation_id,
      });

      return validationResult;

    } catch (error) {
      const processingTime = Date.now() - startTime;

      this.logger.error('文件验证失败', {
        filename: file.originalname,
        user_id: options.user_id,
        error: ExceptionUtils.createUserFriendlyResponse(error),
        processing_time_ms: processingTime,
        correlation_id: options.correlation_id,
      });

      // 清理临时文件
      await this.cleanupTempFile(file.path);

      if (error instanceof FileValidationException) {
        throw error;
      }

      throw new FileValidationException(error.message);
    }
  }

  /**
   * 验证基础文件属性
   */
  private async validateBasicProperties(file: Express.Multer.File): Promise<void> {
    // 文件名验证
    if (!file.originalname || file.originalname.trim().length === 0) {
      throw new FileValidationException('文件名不能为空');
    }

    // 文件名长度限制
    if (file.originalname.length > 255) {
      throw new FileValidationException('文件名过长，最大允许255个字符');
    }

    // 文件名安全字符检查
    const unsafePattern = /[<>:"/\\|?*\x00-\x1f]/;
    if (unsafePattern.test(file.originalname)) {
      throw new FileValidationException('文件名包含不安全字符');
    }

    // 文件扩展名检查
    const fileExtension = path.extname(file.originalname).toLowerCase();
    const allowedExtensions = ['.jpg', '.jpeg', '.png', '.tiff', '.tif', '.bmp'];
    if (!allowedExtensions.includes(fileExtension)) {
      throw new FileValidationException(`不支持的文件扩展名: ${fileExtension}`);
    }

    // MIME类型检查
    if (!this.allowedMimeTypes.includes(file.mimetype)) {
      throw new FileValidationException(`不支持的MIME类型: ${file.mimetype}`);
    }

    // 文件大小检查
    if (file.size === 0) {
      throw new FileValidationException('文件大小不能为0');
    }

    if (file.size > this.maxFileSize) {
      throw new FileValidationException(
        `文件大小超过限制，最大允许 ${this.formatFileSize(this.maxFileSize)}`
      );
    }

    // 验证临时文件是否存在
    try {
      await fs.access(file.path);
    } catch {
      throw new FileValidationException('临时文件不存在或无法访问');
    }
  }

  /**
   * 计算文件哈希
   * 解决隐患：#13 异步I/O操作
   */
  private async calculateFileHash(filePath: string): Promise<string> {
    try {
      const fileBuffer = await fs.readFile(filePath);
      const hash = crypto.createHash('sha256');
      hash.update(fileBuffer);
      return hash.digest('hex');
    } catch (error) {
      throw new FileValidationException('计算文件哈希失败');
    }
  }

  /**
   * 验证图像属性
   * 解决隐患：#13 使用sharp进行异步图像处理
   */
  private async validateImageProperties(filePath: string): Promise<sharp.Metadata> {
    try {
      const metadata = await sharp(filePath).metadata();

      // 验证图像格式
      if (!metadata.format) {
        throw new FileValidationException('无法识别图像格式');
      }

      // 验证图像尺寸
      if (!metadata.width || !metadata.height) {
        throw new FileValidationException('无法获取图像尺寸');
      }

      if (metadata.width < this.minDimensions.width || metadata.height < this.minDimensions.height) {
        throw new FileValidationException(
          `图像尺寸过小，最小要求 ${this.minDimensions.width}x${this.minDimensions.height}`
        );
      }

      if (metadata.width > this.maxDimensions.width || metadata.height > this.maxDimensions.height) {
        throw new FileValidationException(
          `图像尺寸过大，最大允许 ${this.maxDimensions.width}x${this.maxDimensions.height}`
        );
      }

      // 验证色彩空间
      if (metadata.space && !['srgb', 'rgb', 'cmyk', 'lab', 'b-w'].includes(metadata.space)) {
        throw new FileValidationException(`不支持的色彩空间: ${metadata.space}`);
      }

      // 验证位深度
      if (metadata.depth && (metadata.depth < 8 || metadata.depth > 16)) {
        throw new FileValidationException(`不支持的位深度: ${metadata.depth}`);
      }

      this.logger.debug('图像属性验证通过', {
        format: metadata.format,
        width: metadata.width,
        height: metadata.height,
        channels: metadata.channels,
        hasAlpha: metadata.hasAlpha,
      });

      return metadata;

    } catch (error) {
      if (error instanceof FileValidationException) {
        throw error;
      }

      if (error.message.includes('Unsupported') || error.message.includes('invalid')) {
        throw new FileValidationException('图像文件损坏或格式不支持');
      }

      throw new FileValidationException('图像属性验证失败');
    }
  }

  /**
   * 安全保存文件
   * 解决隐患：#13 异步I/O，文件安全处理
   */
  private async saveFileSecurely(
    file: Express.Multer.File,
    fileHash: string,
    options: ValidationOptions,
  ): Promise<string> {
    try {
      // 生成安全的文件路径
      const today = new Date();
      const year = today.getFullYear();
      const month = String(today.getMonth() + 1).padStart(2, '0');
      const day = String(today.getDate()).padStart(2, '0');

      const relativeDir = path.join(options.module, String(year), month, day);
      const targetDir = path.join(this.uploadPath, relativeDir);

      // 创建目标目录
      await fs.mkdir(targetDir, { recursive: true });

      // 生成安全的文件名
      const fileExtension = path.extname(file.originalname);
      const safeFilename = `${fileHash}${fileExtension}`;
      const targetPath = path.join(targetDir, safeFilename);

      // 检查文件是否已存在（哈希冲突检测）
      try {
        await fs.access(targetPath);
        const existingHash = await this.calculateFileHash(targetPath);
        if (existingHash === fileHash) {
          this.logger.info('文件已存在（相同哈希）', {
            file_hash: fileHash.substring(0, 16) + '...',
            existing_path: this.sanitizePath(targetPath),
          });
          return targetPath;
        } else {
          // 哈希冲突，使用不同的文件名
          const conflictedPath = path.join(targetDir, `${fileHash}_${Date.now()}${fileExtension}`);
          await this.copyFileAtomically(file.path, conflictedPath);
          return conflictedPath;
        }
      } catch {
        // 文件不存在，正常保存
        await this.copyFileAtomically(file.path, targetPath);
        return targetPath;
      }

    } catch (error) {
      throw new FileValidationException(`文件保存失败: ${error.message}`);
    }
  }

  /**
   * 原子性文件复制
   * 解决隐患：#13 异步I/O，确保文件完整性
   */
  private async copyFileAtomically(sourcePath: string, targetPath: string): Promise<void> {
    const tempPath = `${targetPath}.tmp.${Date.now()}`;

    try {
      // 复制到临时文件
      await fs.copyFile(sourcePath, tempPath);

      // 验证复制的文件完整性
      const sourceHash = await this.calculateFileHash(sourcePath);
      const tempHash = await this.calculateFileHash(tempPath);

      if (sourceHash !== tempHash) {
        throw new Error('文件复制完整性验证失败');
      }

      // 原子性重命名
      await fs.rename(tempPath, targetPath);

    } catch (error) {
      // 清理临时文件
      try {
        await fs.unlink(tempPath);
      } catch {
        // 忽略清理错误
      }
      throw error;
    }
  }

  /**
   * 清理临时文件
   */
  private async cleanupTempFile(tempPath: string): Promise<void> {
    try {
      await fs.unlink(tempPath);
      this.logger.debug('临时文件已清理', {
        temp_path: this.sanitizePath(tempPath),
      });
    } catch (error) {
      this.logger.warn('清理临时文件失败', {
        temp_path: this.sanitizePath(tempPath),
        error: error.message,
      });
    }
  }

  /**
   * 批量清理过期文件
   * 解决隐患：#6 内存泄漏（磁盘空间）
   */
  async cleanupExpiredFiles(maxAgeDays: number = 30): Promise<void> {
    const cutoffTime = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
    let cleanedCount = 0;
    let totalSizeFreed = 0;

    try {
      const cleanupDirectory = async (dirPath: string): Promise<void> => {
        try {
          const entries = await fs.readdir(dirPath, { withFileTypes: true });

          for (const entry of entries) {
            const fullPath = path.join(dirPath, entry.name);

            if (entry.isDirectory()) {
              await cleanupDirectory(fullPath);
            } else if (entry.isFile()) {
              const stats = await fs.stat(fullPath);

              if (stats.mtime.getTime() < cutoffTime) {
                await fs.unlink(fullPath);
                cleanedCount++;
                totalSizeFreed += stats.size;

                this.logger.debug('清理过期文件', {
                  file_path: this.sanitizePath(fullPath),
                  file_size: stats.size,
                  last_modified: stats.mtime.toISOString(),
                });
              }
            }
          }

          // 尝试删除空目录
          try {
            const remainingEntries = await fs.readdir(dirPath);
            if (remainingEntries.length === 0) {
              await fs.rmdir(dirPath);
            }
          } catch {
            // 忽略目录删除错误
          }

        } catch (error) {
          this.logger.warn('清理目录失败', {
            directory: this.sanitizePath(dirPath),
            error: error.message,
          });
        }
      };

      await cleanupDirectory(this.uploadPath);

      this.logger.log('文件清理完成', {
        cleaned_files: cleanedCount,
        size_freed: this.formatFileSize(totalSizeFreed),
        max_age_days: maxAgeDays,
      });

    } catch (error) {
      this.logger.error('文件清理失败', {
        error: error.message,
        max_age_days: maxAgeDays,
      });
    }
  }

  /**
   * 工具方法
   */
  private formatFileSize(bytes: number): string {
    const units = ['B', 'KB', 'MB', 'GB'];
    let size = bytes;
    let unitIndex = 0;

    while (size >= 1024 && unitIndex < units.length - 1) {
      size /= 1024;
      unitIndex++;
    }

    return `${size.toFixed(1)} ${units[unitIndex]}`;
  }

  private sanitizePath(filePath: string): string {
    if (!filePath) return '';
    return filePath
      .replace(/[A-Za-z]:\\/g, '[驱动器]:\\')
      .replace(/\\/g, '/')
      .replace(/\/[^\/]+\/([^\/]+)$/, '/.../$1');
  }

  /**
   * 健康检查
   */
  async healthCheck(): Promise<any> {
    try {
      await fs.access(this.uploadPath);
      const stats = await fs.stat(this.uploadPath);

      return {
        service: 'file-validation',
        status: 'healthy',
        upload_path: this.uploadPath,
        max_file_size: this.maxFileSize,
        allowed_mime_types: this.allowedMimeTypes,
        directory_accessible: true,
        directory_stats: {
          exists: true,
          writable: true,
          last_modified: stats.mtime.toISOString(),
        },
        timestamp: new Date().toISOString(),
      };

    } catch (error) {
      return {
        service: 'file-validation',
        status: 'unhealthy',
        upload_path: this.uploadPath,
        error: error.message,
        timestamp: new Date().toISOString(),
      };
    }
  }
}